
/*
 *  This program is free software: you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation, either version 3 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 *
 *  You should have received a copy of the GNU General Public License
 *  along with this program.  If not, see <http://www.gnu.org/licenses/>.
 *
 * SPDX-License-Identifier: GPL-3.0+
 * License-Filename: LICENSE
 *
 */

#include "config.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <math.h>

#include "splay-tree.h"
#include "main.h"
#include "parse.h"
#include "parsedot.h"
#include "mem.h"
#include "options.h"
#include "color.h"
#include "uniqstring.h"
#include "uniqnode.h"
#include "nes.h"
#include "folding.h"

/* tokens and lexer set new lastxxxx strings */
int token = 0;

/* next token */
static int token1 = 0;
static char *token1_laststring = (char *)0;
static char *token1_lastid = (char *)0;
static char *token1_lastnum = (char *)0;
static char *token1_lastchar = (char *)0;
static char *token1_lasthtml = (char *)0;

/* previous token */
static int prev_token = 0;
static char *prev_laststring = (char *)0;
static char *prev_lastid = (char *)0;
static char *prev_lastnum = (char *)0;
static char *prev_lastchar = (char *)0;
static char *prev_lasthtml = (char *)0;

/* counters */
static int ncompound = 0;
int nsubgraph = 0;

/* pending edge tree */
static splay_tree pet = (splay_tree) 0;
static int npet = 0;

/* delayed edges */
static splay_tree destree = (splay_tree) 0;
static int ndes = 0;

/* root graph default edge line color black */
int ed_color = 0;

/* root graph default edge font text color black */
int ed_fcolor = 0;

/* root graph edge label font name */
char *ed_ffontname = NULL;

/* root graph edge line thickness */
unsigned char ed_thickness = 0;

/* root graph edge label */
char *ed_label = NULL;

/* root graph edge style */
int ed_style = ESTYLE_NORMAL;

/* root graph edge label font size */
char *ed_fontsize = NULL;

/* root graph edge color scheme code */
int ed_cs = 0;

/* root graph node url */
char *nd_url = NULL;

/* root graph node border color */
int nd_bcolor = 0;

/* root graph node color scheme code */
int nd_cs = 0;

/* node default fill color */
int nd_fillcolor = 0x00ffffff;	/* white */

/* node default label */
char *nd_label = NULL;

/* node default typ of label */
unsigned char nd_labeltype = 0;

/* node default shape "node[shape="name"]" */
int nd_shape = 0;

/* node default fontsize "node[fontsize=10]" NULL is gtk default */
char *nd_fontsize = NULL;

/* node default fontname "node[fontname=name]" NULL is gtk default */
char *nd_fontname = NULL;

/* node default style=filled */
unsigned char nd_stylefilled = 0;

/* forward declarations */
static char *is_a_dot_escstring_node_record(char *str, struct unode *un);
static int dot_desexpand1(splay_tree_node n, void *data);
static int is_a_dot_background(struct usubg *subg);
static int is_a_dotbb(struct usubg *subg);
static int is_a_dotbgcolor(struct usubg *subg);
static int is_a_dotcedge(struct usubg *subg);
static int is_a_dotcenter(struct usubg *subg);
static int is_a_dotcharset(struct usubg *subg);
static int is_a_dotclusterrank(struct usubg *subg);
static int is_a_dotcolorscheme(struct usubg *subg);
static int is_a_dotcolor(struct usubg *subg);
static int is_a_dotcomment(struct usubg *subg);
int is_a_dotcompoundgraph(struct usubg *subg);
static int is_a_dotcompound(struct usubg *subg);
static int is_a_dotconcentrate(struct usubg *subg);
static int is_a_dotdamping(struct usubg *subg);
static int is_a_dotdefaultdist(struct usubg *subg);
static int is_a_dotdimen(struct usubg *subg);
static int is_a_dotdim(struct usubg *subg);
static int is_a_dotdiredgeconstraints(struct usubg *subg);
static int is_a_dotdpi(struct usubg *subg);
static int is_a_dotepsilon(struct usubg *subg);
static int is_a_dotesep(struct usubg *subg);
static int is_a_dotfillcolor(struct usubg *subg);
static int is_a_dotfontcolor(struct usubg *subg);
static int is_a_dotfontnames(struct usubg *subg);
static int is_a_dotfontname(struct usubg *subg);
static int is_a_dotfontpath(struct usubg *subg);
static int is_a_dotfontsize(struct usubg *subg);
static int is_a_dotforcelabels(struct usubg *subg);
static int is_a_dotgradientangle(struct usubg *subg);
static int is_a_dotgraphdefault(struct usubg *subg);
static int is_a_dothref(struct usubg *subg);
static int is_a_dotid(struct usubg *subg);
static int is_a_dotimagepath(struct usubg *subg);
static int is_a_dotinputscale(struct usubg *subg);
static int is_a_dotk(struct usubg *subg);
static int is_a_dotlabeljust(struct usubg *subg);
static int is_a_dotlabelloc(struct usubg *subg);
static int is_a_dotlabel_scheme(struct usubg *subg);
static int is_a_dotlabel(struct usubg *subg);
static int is_a_dotlandscape(struct usubg *subg);
static int is_a_dotlayerlistsep(struct usubg *subg);
static int is_a_dotlayerselect(struct usubg *subg);
static int is_a_dotlayersep(struct usubg *subg);
static int is_a_dotlayers(struct usubg *subg);
static int is_a_dotlayout(struct usubg *subg);
static int is_a_dotlevelsgap(struct usubg *subg);
static int is_a_dotlevels(struct usubg *subg);
static int is_a_dotlheight(struct usubg *subg);
static int is_a_dotlp(struct usubg *subg);
static int is_a_dotlwidth(struct usubg *subg);
static int is_a_dotmargin(struct usubg *subg);
static int is_a_dotmaxiter(struct usubg *subg);
static int is_a_dotmclimit(struct usubg *subg);
static int is_a_dotmindist(struct usubg *subg);
static int is_a_dotmodel(struct usubg *subg);
static int is_a_dotmode(struct usubg *subg);
static int is_a_dotmosek(struct usubg *subg);
static int is_a_dotnodesep(struct usubg *subg);
static int is_a_dotnojustify(struct usubg *subg);
static int is_a_dotnormalize(struct usubg *subg);
static int is_a_dotnotranslate(struct usubg *subg);
static int is_a_dotnslimit1(struct usubg *subg);
static int is_a_dotnslimit(struct usubg *subg);
static int is_a_dotordering(struct usubg *subg);
static int is_a_dotorientation(struct usubg *subg);
static int is_a_dotoutputorder(struct usubg *subg);
static int is_a_dotoverlap_scaling(struct usubg *subg);
static int is_a_dotoverlap_shrink(struct usubg *subg);
static int is_a_dotoverlap(struct usubg *subg);
static int is_a_dotpackmode(struct usubg *subg);
static int is_a_dotpack(struct usubg *subg);
static int is_a_dotpad(struct usubg *subg);
static int is_a_dotpagedir(struct usubg *subg);
static int is_a_dotpage(struct usubg *subg);
static int is_a_dotpencolor(struct usubg *subg);
static int is_a_dotpenwidth(struct usubg *subg);
static int is_a_dotquadtree(struct usubg *subg);
static int is_a_dotquantum(struct usubg *subg);
static int is_a_dotrankdir(struct usubg *subg);
static int is_a_dotranksep(struct usubg *subg);
static int is_a_dotrank(struct usubg *subg);
static int is_a_dotratio(struct usubg *subg);
static int is_a_dotremincross(struct usubg *subg);
static int is_a_dotrepulsiveforce(struct usubg *subg);
static int is_a_dotresolution(struct usubg *subg);
static int is_a_dotroot(struct usubg *subg);
static int is_a_dotrotate(struct usubg *subg);
static int is_a_dotrotation(struct usubg *subg);
static int is_a_dotsamplepoints(struct usubg *subg);
static int is_a_dotscale(struct usubg *subg);
static int is_a_dotsearchsize(struct usubg *subg);
static int is_a_dotsep(struct usubg *subg);
static int is_a_dotshowboxes(struct usubg *subg);
static int is_a_dotsize(struct usubg *subg);
static int is_a_dotsmoothing(struct usubg *subg);
static int is_a_dotsortv(struct usubg *subg);
static int is_a_dotsplines(struct usubg *subg);
static int is_a_dotstart(struct usubg *subg);
static int is_a_dotstylesheet(struct usubg *subg);
static int is_a_dotstyle(struct usubg *subg);
static int is_a_dottarget(struct usubg *subg);
static int is_a_dottruecolor(struct usubg *subg);
static int is_a_doturl(struct usubg *subg);
static int is_a_dotviewport(struct usubg *subg);
static int is_a_dotvoro_margin(struct usubg *subg);
static int is_a_dotxdotversion(struct usubg *subg);
static void dot_clrv(splay_tree_value v);
static void dot_desclear(void);
static void dot_desexpand1e(char *fname, char *tname);
static void dot_desexpand1ns(char *fname, struct usubg *tsubg);
static void dot_desexpand1sn(struct usubg *fsubg, char *tname);
static void dot_desexpand1ss(struct usubg *fsubg, struct usubg *tsubg);
static void dot_desexpand(void);
static void dot_lexprint(int token, char *via);
static void dot_pe_free(void);
static void set_graph_default_fdefaults(void);
static void set_node_default_fdefaults(void);
static void set_edge_default_fdefaults(void);

/* print a token */
static void dot_lexprint(int ttoken, char *via)
{
	char *str = (char *)0;
	char *tstr = (char *)0;
	if (option_parsedebug == 0) {
		return;
	}
	switch (ttoken) {
	case 0:
		tstr = "0";
		str = "<<EOF>>";
		break;
	case DOT_UTF8BOM:
		tstr = "utf8bom";
		str = "utf8bom";
		break;
	case DOT_UEDGE:
		tstr = "DOT_UEDGE";
		str = "--";
		break;
	case DOT_DEDGE:
		tstr = "DOT_DEDGE";
		str = "->";
		break;
	case DOT_BO:
		tstr = "{";
		str = "{";
		break;
	case DOT_BC:
		tstr = "}";
		str = "}";
		break;
	case DOT_BRACKETO:
		tstr = "[";
		str = "[";
		break;
	case DOT_BRACKETC:
		tstr = "]";
		str = "]";
		break;
	case DOT_COMMA:
		tstr = ",";
		str = ",";
		break;
	case DOT_SEMIC:
		tstr = ";";
		str = ";";
		break;
	case DOT_COLON:
		tstr = ":";
		str = ":";
		break;
	case DOT_IS:
		tstr = "=";
		str = "=";
		break;
	case DOT_HTML:
		tstr = "DOT_HTML";
		str = lasthtml;
		break;
	case DOT_STRING:
		tstr = "DOT_STRING";
		str = laststring;
		break;
	case DOT_ID:
		tstr = "DOT_ID";
		str = lastid;
		break;
	case DOT_NUM:
		tstr = "DOT_NUM";
		str = lastnum;
		break;
	case DOT_CHAR:
		tstr = "DOT_CHAR";
		str = lastchar;
		break;
	default:
		tstr = "<unknown>";
		str = "<unknown-token>";
		break;
	}
	printf("%s():changed token via %s in token=%s string=\"%s\"\n", __FUNCTION__, via, tstr, str);
	fflush(stdout);
	return;
}

/* unlex a token */
void dot_unlex(void)
{

	if (option_parsedebug) {
		printf("%s(): saving token1=%d switch to token=%d\n", __FUNCTION__, token, prev_token);
		fflush(stdout);
	}

	/* pushback token */
	token1_laststring = laststring;
	token1_lastid = lastid;
	token1_lastnum = lastnum;
	token1_lasthtml = lasthtml;
	token1_lastchar = lastchar;
	token1 = token;

	/* restore token */
	laststring = prev_laststring;
	lastid = prev_lastid;
	lastnum = prev_lastnum;
	lasthtml = prev_lasthtml;
	lastchar = prev_lastchar;
	token = prev_token;

	return;
}

/* wrapped yylex */
int dot_lex(void)
{
	int ret = 0;
	char *via = (char *)0;

	/* backup current token */
	prev_token = token;
	prev_laststring = laststring;
	prev_lastid = lastid;
	prev_lastnum = lastnum;
	prev_lasthtml = lasthtml;
	prev_lastchar = lastchar;

	/* check if a backup */
	if (token1) {
		laststring = token1_laststring;
		lastid = token1_lastid;
		lastnum = token1_lastnum;
		lasthtml = token1_lasthtml;
		lastchar = token1_lastchar;
		ret = token1;
		via = "dot_lex(token1)";
		token1 = 0;
	} else {
		laststring = NULL;
		lastid = NULL;
		lastnum = NULL;
		lasthtml = NULL;
		lastchar = NULL;
		/* lexer set new lastxxxx values */
		ret = dotlex();
		via = "dot_lex()";
	}

	dot_lexprint(ret, via);

	return (ret);
}

/*
 * label text is between <TD>label-text</TD>
 * passed string is between '<' '>' start is "<<" end is ">>"
 * example what Linux sparse compiler generates:
 * label=<<TABLE BORDER="0" CELLBORDER="0">
 * <TR><TD>main.c</TD></TR>
 * <TR><TD><FONT POINT-SIZE="21">main()</FONT></TD></TR>
 * </TABLE>>];
 * to implement html labels can be a usable idea and that
 * will need complete re-write of text drawing routines
 * so that is for the next version.
 */
char *is_a_dot_htmlstring_node(char *str, struct unode *un)
{
	int maxc = 0;
	char *buf = NULL;
	int i = 0;
	int nest = 0;
	char c = 0;
	char *p = NULL;
	/* check string */
	if (un) {
	}
	if (str == NULL) {
		printf("%s(): str is NULL\n", __FUNCTION__);
		fflush(stdout);
		return (NULL);
	}
	if (strlen(str) == 0) {
		return ("");
	}
	/* if only <> */
	if (strlen(str) <= 2) {
		return ("");
	}

	/* fixme this needs a new parser */
	printf("%s(): html string not-yet implemented\n", __FUNCTION__);
	fflush(stdout);

	/* how long can the string be from lexer */
	maxc = dotlex_max_strlen();
	/* buffer for new text */

	buf = mymalloc(maxc, __FUNCTION__, __LINE__);

	/* buf is assumed to be zeroed memory */
	i = 0;
	p = str;
	/* skip first ',' */
	p = (p + 1);
	/* nesting level */
	nest = 1;
	while ((*p)) {
		c = (char)(*p);
		if (c == '>') {
			nest = (nest - 1);
			p = (p + 1);
			continue;
		}
		if (c == '<') {
			nest = (nest + 1);
			p = (p + 1);
			continue;
		}
		if (nest == 1) {
			/* skip ws between <> <> */
			if (c == '\n') {
				p = (p + 1);
				continue;
			}
			/* skip ws between <> <> */
			if (c == ' ') {
				p = (p + 1);
				continue;
			}
			/* real char between <> <> */
			buf[i] = c;
			i = (i + 1);
			if (i >= (maxc - 1)) {
				break;
			}
			/* if new tag follows add \n */
			if (*(p + 1) == '<') {
				buf[i] = '\n';
				i = (i + 1);
				if (i >= (maxc - 1)) {
					break;
				}
			}
		}
		p = (p + 1);
	}
	/* delete last \n if any */
	if (i > 1) {
		if (buf[i - 1] == '\n') {
			buf[i - 1] = '\0';
		}
	}
	/* create fresh copy of new string */
	p = uniqstring(buf);
	i = strlen(buf);
	nest = strlen(str);
	myfree(buf, __FUNCTION__, __LINE__);
	/* return the new formatted and buffered string */
	if (option_parsedebug) {
		printf("%s(): reformatted %d chars `%s' from `%s' %d char\n", __FUNCTION__, i, p, str, nest);
		fflush(stdout);
	}
	return (p);
}

/* max strlen for a field string, high value in llvm dot data needed */
#define RBUFTZ 1024*64

/* max strlen() for a port string */
#define RBUFPZ 128

/* */
static int is_a_dot_escstring_node_record_errno = 0;

/* */
static char *recordstr = NULL;

/* strip leading sw mangle string */
static char *is_a_dot_escstring_node_record_tstrip(char *str, struct unode *un)
{
	char text[RBUFTZ];
	int tcount = 0;
	char *p = NULL;
	char *q = NULL;
	char *ret = NULL;
	memset(text, 0, RBUFTZ);
	tcount = 0;
	p = str;
	/* short string */
	if (strlen(str) == 1) {
		text[0] = (*p);
		/* make sure "'\n'" becomes a single space " " with input "\l" */
		if (text[0] == '\n') {
			text[0] = ' ';
		}
		ret = uniqstring(text);
		return ((char *)ret);
	}
	/* skip leading spaces and few special chars */
	while (*p) {
		if (*p == '\0') {
			/* string with only ws */
			text[0] = ' ';
			tcount = 1;
			break;
		}
		/* check esc chars */
		if (*p == '\\') {
			p++;
			if (*p == '\0') {
				text[0] = '\\';
				tcount = 1;
				break;
			} else if (*p == 'l') {
				/* skip left align */
			} else if (*p == 'n') {
				/* skip center align */
			} else if (*p == 'r') {
				/* skip right align */
			} else if (*p == '\n') {
				/* skip line cont. */
			} else if (*p == ' ') {
				/* skip space */
			} else if (*p == 'N') {
				/* to nodename */
				p--;
				break;
			} else {
				/* note few more todo here */
				p--;
				break;
			}
		} else if (*p == ' ') {
			/* skip sp */
		} else {
			/* first char */
			break;
		}
		p++;
	}
	/* copy rest of input string */
	if (*p) {
		while (*p) {
			if (*p == '\\') {
				p++;
				/* nodename copy */
				if (*p == 'N') {
					q = un->name;
					if (q) {
						while (*q) {
							if (tcount < (RBUFTZ - 1)) {
								text[tcount] = (char)(*q);
								tcount++;
							}
							q++;
						}
					}
				} else if (*p == ' ') {
					if (tcount < (RBUFTZ - 1)) {
						text[tcount] = (char)(*p);
						tcount++;
					}
				} else if (*p == '<') {
					if (tcount < (RBUFTZ - 1)) {
						text[tcount] = (char)(*p);
						tcount++;
					}
				} else if (*p == '>') {
					if (tcount < (RBUFTZ - 1)) {
						text[tcount] = (char)(*p);
						tcount++;
					}
				} else if (*p == '{') {
					if (tcount < (RBUFTZ - 1)) {
						text[tcount] = (char)(*p);
						tcount++;
					}
				} else if (*p == '}') {
					if (tcount < (RBUFTZ - 1)) {
						text[tcount] = (char)(*p);
						tcount++;
					}
				} else if (*p == 'l') {
					/* left align skip */
				} else if (*p == 'n') {
					/* center align skip */
				} else if (*p == 'r') {
					/* right align skip */
				} else if (*p == '\n') {
					/* line cont skip */
				} else {
					/* other esc char copy */
					if (tcount < (RBUFTZ - 2)) {
						text[tcount] = '\\';
						tcount++;
						text[tcount] = (char)(*p);
						tcount++;
					}
				}
			} else {
				/* regular char */
				if (tcount < (RBUFTZ - 1)) {
					text[tcount] = (char)(*p);
					tcount++;
				}
			}
			p++;
		}
	}
	/* now create a fresh copy */
	if (tcount >= 1) {
		/* delete last '\n' */
		if (text[tcount - 1] == '\n') {
			text[tcount - 1] = '\0';
			if ((tcount - 1) > 1) {
				tcount--;
			}
			/* make sure "" is a " " */
			if (text[tcount - 1] == '\0') {
				text[0] = ' ';
				text[1] = '\0';
				tcount = 1;
			}
		}
	}
	/* strdup() a fresh copy */
	if (strlen(text) == 0) {
		text[0] = ' ';
		text[1] = '\0';
		tcount = 1;
	}
	/* at "\l" --> " " */
	if (text[0] == '\n' && tcount == 1) {
		text[0] = ' ';
		text[1] = '\0';
		tcount = 1;
	}
	ret = uniqstring(text);
	if (option_parsedebug || 0) {
		printf("%s(): text=\"%s\" %d chars and last char \"%c\"\n", __FUNCTION__, text, tcount, text[tcount - 1]);
		fflush(stdout);
	}
	return ((char *)ret);
}

/* check exactly one of \l \n \r in string
 * but there can be multiple \l as in GCC data:
fn_10_basic_block_31 [shape=record,style=filled,fillcolor=lightgrey,label="{ FREQ:0 |\<bb\ 31\>:\l\
|_59\ =\ m2_7\ -\ m1_34;\l\
|_60\ =\ 2.4e+2\ -\ hue_4;\l\
|_61\ =\ _59\ *\ _60;\l\
|_62\ =\ _61\ /\ 6.0e+1;\l\
|g_63\ =\ _62\ +\ m1_34;\l\
|#\ DEBUG\ g\ =\>\ g_63\l\
goto\ \<bb\ 33\>;\l\
}"];
 * the last line with "debug" has twice \l
 * so here multiple \l in one line is allowed
 * the first \l must be translated in newline char '\n'
 * return is 4 if multiple \l \n \r or multiple \l combined with \n or \r
 */
static int is_a_dot_escstring_node_record_align(char *str)
{
	char *p = NULL;
	char cseen = 0;
	int ret = 0;
	if (str == NULL) {
		return (0);
	}
	if (strlen(str) == 0) {
		return (0);
	}
	p = str;
	ret = 0;
	while (*p) {
		/* check \l \n \r */
		if (*p == '\\') {
			p++;
			if (*p == '\0') {
				break;
			}
			if (*p == 'l') {
				if (ret) {
					if (cseen == *p) {
						/* multiple \l allowed */
						ret = 1;
						cseen = 'l';
					} else {
						/* \l with other is multiple */
						ret = 4;
						break;
					}
				}
				ret = 1;
				cseen = 'l';
			} else if (*p == 'n') {
				if (ret) {
					ret = 4;
					break;
				}
				ret = 2;
			} else if (*p == 'r') {
				if (ret) {
					ret = 4;
					break;
				}
				ret = 3;
			} else {	/* other is oke */
			}
		}
		p++;
	}
	/* 0=none,1=\l,2=\n3=\r,4=multiple */
	return (ret);
}

/* count fields up to matching close brace */
static int is_a_dot_escstring_node_record_r_nf(char *str)
{
	char cc = 0;
	int ret = 0;
	int ind = 0;
	char *pq = NULL;
	ret = 0;
	ind = 0;

	if (str == NULL) {
		return (0);
	}
	if (strlen(str) == 0) {
		return (0);
	}
	pq = str;
	for (;;) {
		cc = (*pq);
		if (cc == '\0') {
			break;
		}
		/* skip esc char */
		if ((cc) == '\\') {
			pq++;
			cc = (*pq);
			if (cc == '\0') {
				/* early eos */
				break;
			}
			pq++;
			continue;
		}
		if (cc == '{') {
			ind++;
		} else if (cc == '}') {
			ind--;
			if (ind < 0) {
				/* matching close found */
				break;
			}
		} else if (cc == '|') {
			if (ind == 0) {
				ret++;
			}
		}
		if (ind < 0) {
			break;
		}
		pq++;
	}

	if (option_parsedebug || 0) {
		printf("%s(): %d fields for \"%s\"\n", __FUNCTION__, (ret + 1), str);
		fflush(stdout);
	}

	return (ret);
}

/* */
static int ind = 0;

/* recursive handle special string with {|} format chars. needs to be re-written */
static struct fld *is_a_dot_escstring_node_record_r(char *str, struct unode *un, int dir)
{
	struct fld *rf = NULL;
	unsigned prevcc = '\0';
	unsigned char cc = '\0';
	char text[RBUFTZ];	/* buffer for label text */
	char ptext[RBUFPZ];	/* buffer for port text */
	int tcount = 0;		/* number of chars in label text */
	int pcount = 0;		/* number of chars in port text */
	int align = 0;		/* type of label text alignment */
	int nf = 0;		/* number of fields seen */
	int nch = 0;		/* number of chars seen */
	int portoke = 0;	/* port statement is oke */
	char *lastport = NULL;	/* last seen port text */
	char *lastlabel = NULL;	/* stripped last seen label */
	struct fld *tmpfld = NULL;	/* field */
	int ix = 0;		/* index in field pointer array */
	int dbg = 0;		/* extra debug output */

	/* check and handle max. {} nesting level here */
	memset(text, 0, RBUFTZ);

	tcount = 0;

	memset(ptext, 0, RBUFPZ);

	pcount = 0;

	align = 0;

	lastlabel = NULL;
	lastport = NULL;

	/* how many fields in sub string */
	nf = is_a_dot_escstring_node_record_r_nf(str);

	tmpfld = NULL;
	ix = 0;
	nch = 0;
	portoke = 0;
	dbg = 0;

	/* optional extra debug output */
	if (option_parsedebug > 1) {
		dbg = 1;
	}

	/* data struct itself */
	rf = (struct fld *)mymalloc(sizeof(struct fld), __FUNCTION__, __LINE__);

	/* extra +1 for terminating NULL pointer */
	nf++;			/* range 1...n instead of 0...n */

	rf->nf = nf;		/* number of active pointers in rf->f[...] */

	nf++;			/* additional NULL termintor */

	/* number of fields pointers */
	rf->f = mymalloc((sizeof(void *)) * nf, __FUNCTION__, __LINE__);

	if (option_parsedebug || 0) {
		printf("%s(): node-number=%d for %p %d fields alloc (%d) rf->f=%p dir=%d str=\"%s\"\n", __FUNCTION__, un->number,
		       (void *)rf, nf, (int)sizeof(void *), (void *)rf->f, dir, str);
	}

	for (;;) {

		/* check parse error */
		if (is_a_dot_escstring_node_record_errno) {
			/* stop parse */
			break;
		}
		/* handle chars as:
		 * '<' until '>' as a port
		 *'|' as separator
		 *'{' as field separator
		 *'}' as end of field
		 * any-other-char is label
		 * and handle '\' esc chars
		 * ignore chars below 0x20 ' '
		 */

		/* backup previous char */
		prevcc = cc;
		cc = (unsigned char)(*recordstr);

		/* ignore control char but not 0x00 */
		if ((cc < ' ') && (cc > '\0')) {
			recordstr++;
			continue;
		}

		/* number of chars parsed */
		nch++;

		/* check end of string */
		if (cc == '\0') {
			if (prevcc == '|') {
				/* example "aa|" adds empty space */
				text[0] = ' ';
				text[1] = '\0';
				tcount = 1;
			}
			/* at "...}" */
			if (tcount == 0) {
				if (dbg) {
					printf("%s(): rf=%p ix=%d end-of-line tcount=0\n", __FUNCTION__, (void *)rf, ix);
				}
				if (ix > rf->nf) {
					printf("%s(line=%d): ix=%d nf=%d shouldnothappen\n", __FUNCTION__, __LINE__, ix, rf->nf);
				} else {
					rf->f[ix] = NULL;	/* make sure terminated with a NULL pointer */
				}

				break;
			}

			/* check \l \n \r text alignment */
			align = is_a_dot_escstring_node_record_align(text);
			switch (align) {
			case 0:	/* none */
				break;
			case 1:	/* \l left */
				break;
			case 2:	/* \n middle */
				break;
			case 3:	/* \r right */
				break;
			case 4:	/* multiple */
				break;
			default:	/* should  not happen */
				break;
			}
			/* this is allowed in dot */
			if (0) {
				if (align == 4) {
					/* multiple \l \n \r detected */
					is_a_dot_escstring_node_record_errno = 6;
					break;
				}
			}

			if (text[0]) {
				/* strip format chars */
				lastlabel = is_a_dot_escstring_node_record_tstrip(text, un);

				if (strlen(lastlabel)) {
					/* fresh struct */
					tmpfld = (struct fld *)mymalloc(sizeof(struct fld), __FUNCTION__, __LINE__);
					/* set params */
					tmpfld->port = lastport;
					tmpfld->label = lastlabel;
					tmpfld->dir = dir;	/* direction */
					/* add to list of pointers */
					if (dbg) {
						printf("%s(): rf=%p ix=%d tmpfld=%p at-0 label=\"%s\"\n", __FUNCTION__, (void *)rf,
						       ix, (void *)tmpfld, lastlabel);
					}
					if (ix > rf->nf) {
						printf("%s(line=%d): ix=%d nf=%d shouldnothappen\n", __FUNCTION__, __LINE__, ix,
						       rf->nf);
					} else {
						rf->f[ix] = tmpfld;
					}
					ix++;	/* to next index */
				}
			}

			lastport = NULL;
			/* eos */
			break;
		}
		/* copy esc chars
		 * at end of line at '\' is allowed
		 */
		if (cc == '\\') {
			if (tcount < (RBUFTZ - 2)) {
				recordstr++;
				prevcc = cc;
				cc = (unsigned char)(*recordstr);
				nch++;
				if (cc == '\0') {
					/* allow '\' at end of string */
					text[tcount] = '\\';
					tcount++;
					break;
				}
				/* change \l into '\n' if at middle of string */
				if ((cc == 'l') || (cc == 'n') || (cc == 'r')) {
					/* \l in middle of line replaced with '\n' */
					text[tcount] = '\n';
					tcount++;
					recordstr++;
				} else {
					/* plain copy esc char */
					text[tcount] = '\\';
					tcount++;
					text[tcount] = cc;
					tcount++;
					recordstr++;
				}
				continue;
			} else {
				/* buffer overflow and skip the esc char */
				is_a_dot_escstring_node_record_errno = 4;
				recordstr++;
				continue;
			}
		}
		/* check for <port>
		 * the "<port>" can be anywhere but dot
		 * wants it at start of the substring
		 * example: <f1>aa|<f2>bb
		 * if not at start parse error can be here
		 * or whole tag can be ignored
		 */
		if (cc == '<') {
			/* there can only be 1 <port> */
			if (pcount) {
				is_a_dot_escstring_node_record_errno = 5;
				continue;
			}
			/* check if begin of string and allow a leading space, tofix the leading ws */
			if ((nch == 1) || (nch == 2)) {
				portoke = 1;
			} else {
				portoke = 0;	/* port at wrong pos */
				/* <port> tag not at begin of string */
				is_a_dot_escstring_node_record_errno = 7;
				continue;
			}
			recordstr++;
			prevcc = cc;
			cc = (unsigned char)(*recordstr);
			nch++;
			/* scan */
			for (;;) {
				if (cc == '\0') {
					is_a_dot_escstring_node_record_errno = 3;
					/* unexpected eos */
					break;
				}
				if (cc == '>') {
					recordstr++;
					prevcc = cc;
					cc = (unsigned char)(*recordstr);
					nch++;
					break;
				}
				/* copy char as port string */
				if (pcount < (RBUFPZ - 1)) {
					ptext[pcount] = cc;
					pcount++;
				} else {	/* too long string */
					is_a_dot_escstring_node_record_errno = 4;
					continue;
				}
				recordstr++;
				prevcc = cc;
				cc = (unsigned char)(*recordstr);
				nch++;
			}
			/* check for error */
			if (is_a_dot_escstring_node_record_errno) {
				/* at error skip */
				continue;
			}
			/* is at begin of string ? */
			if (portoke == 0) {
				continue;	/* not at begin of string */
			}
			/* the port string can be "" */
			if (pcount == 0) {
				continue;
			}
			if (option_parsedebug) {
				printf("%s(): found port=\"%s\"\n", __FUNCTION__, ptext);
				fflush(stdout);
			}
			/* do strdup() if needed */
			lastport = uniqstring(ptext);
			continue;
		}
		/* check for special char { | or regular char */
		if (cc == '{') {
			ind++;
			recordstr++;

			/* save direction */
			rf->dir = dir;

			/* toggle direction */
			if (dir == 0) {
				if (dbg) {
					printf("%s(): rf=%p ix=%d recurse1\n", __FUNCTION__, (void *)rf, ix);
				}
				tmpfld = is_a_dot_escstring_node_record_r(recordstr, un, 1 /* top-to-bottom */ );
				if (dbg) {
					printf("%s(): rf=%p ix=%d tmpfld=%p recurse1-result\n", __FUNCTION__, (void *)rf, ix,
					       (void *)tmpfld);
				}
				if (ix > rf->nf) {
					printf("%s(line=%d): ix=%d nf=%d shouldnothappen\n", __FUNCTION__, __LINE__, ix, rf->nf);
				} else {
					rf->f[ix] = tmpfld;
				}
				ix++;
			} else {
				if (dbg) {
					printf("%s(): rf=%p ix=%d recurse2\n", __FUNCTION__, (void *)rf, ix);
				}
				tmpfld = is_a_dot_escstring_node_record_r(recordstr, un, 0 /* left-to-right */ );
				if (dbg) {
					printf("%s(): rf=%p ix=%d tmpfld=%p recurse2-result\n", __FUNCTION__, (void *)rf, ix,
					       (void *)tmpfld);
				}
				if (ix > rf->nf) {
					printf("%s(line=%d): ix=%d nf=%d shouldnothappen\n", __FUNCTION__, __LINE__, ix, rf->nf);
				} else {
					rf->f[ix] = tmpfld;
				}
				ix++;
			}
		} else if (cc == '}') {
			/* correct sp before } */
			if (text[0] == ' ') {
				tcount = 0;
				text[0] = 0;
			}
			/* check \l \n \r text alignment */
			align = is_a_dot_escstring_node_record_align(text);
			switch (align) {
			case 0:	/* none */
				break;
			case 1:	/* \l left */
				break;
			case 2:	/* \n middle */
				break;
			case 3:	/* \r right */
				break;
			case 4:	/* multiple */
				break;
			default:	/* should  not happen */
				break;
			}
			/* this is allowed in dot */
			if (0) {
				if (align == 4) {
					/* error multiple text alignments */
					is_a_dot_escstring_node_record_errno = 6;
					lastport = NULL;
					break;
				}
			}
			/* */
			ind--;

			if (text[0]) {
				/* strip format chars */
				lastlabel = is_a_dot_escstring_node_record_tstrip(text, un);

				if (strlen(lastlabel)) {	/* fresh struct */
					tmpfld = (struct fld *)mymalloc(sizeof(struct fld), __FUNCTION__, __LINE__);
					/* set params */
					tmpfld->port = lastport;
					tmpfld->label = lastlabel;
					tmpfld->dir = dir;	/* direction */
					/* add to list of pointers */
					if (dbg) {
						printf("%s(): rf=%p ix=%d close-brace tmpfld=%p label=\"%s\"\n", __FUNCTION__,
						       (void *)rf, ix, (void *)tmpfld, lastlabel);
					}
					if (ix > rf->nf) {
						printf("%s(line=%d): ix=%d nf=%d shouldnothappen\n", __FUNCTION__, __LINE__, ix,
						       rf->nf);
					} else {
						rf->f[ix] = tmpfld;
					}
					ix++;	/* to next index */
				}
			}

			/* clear old label */
			memset(text, 0, RBUFTZ);
			tcount = 0;
			recordstr++;
			lastport = NULL;
			break;
		} else if (cc == '|') {
			/* reset <port> pos. counter */
			nch = 0;
			portoke = 0;
			/* allow spaces after '|' to skip */
			/* at | at start of line */
			if (prevcc == '\0') {
				text[0] = ' ';
				text[1] = '\0';
				tcount = 1;
			}
			/* at || */
			if (prevcc == '|') {
				text[0] = ' ';
				text[1] = '\0';
				tcount = 1;
			}
			/* optional port */
			if (pcount) {
				/* port string is in ptext */
				if (option_parsedebug) {
					printf("%s(): port after '|' is \"%s\"\n", __FUNCTION__, ptext);
					fflush(stdout);
				}
			}
			/* optional field text */
			if (text[0]) {
				/* strip format chars */
				lastlabel = is_a_dot_escstring_node_record_tstrip(text, un);
				if (strlen(lastlabel)) {
					/* fresh struct */
					tmpfld = (struct fld *)mymalloc(sizeof(struct fld), __FUNCTION__, __LINE__);
					/* set params */
					tmpfld->port = lastport;
					/* set text label */
					tmpfld->label = lastlabel;
					tmpfld->dir = dir;	/* direction */
					if (dbg) {
						printf("%s(): rf=%p ix=%d at-or tmpfld=%p label=\"%s\"\n", __FUNCTION__, (void *)rf,
						       ix, (void *)tmpfld, lastlabel);
					}
					/* add to list of pointers */
					if (ix > rf->nf) {
						printf("%s(line=%d): ix=%d nf=%d shouldnothappen\n", __FUNCTION__, __LINE__, ix,
						       rf->nf);
					} else {
						rf->f[ix] = tmpfld;
					}
					ix++;	/* to next index */
				}
			}
			/* reset port */
			memset(ptext, 0, RBUFPZ);
			pcount = 0;
			/* reset text */
			memset(text, 0, RBUFTZ);
			tcount = 0;
			recordstr++;
			lastport = NULL;
		} else {
			recordstr++;
			if (tcount < (RBUFTZ - 1)) {
				text[tcount] = cc;
				tcount++;
			} else {	/* too long and stop */
				is_a_dot_escstring_node_record_errno = 4;
			}
		}
	}

	return ((struct fld *)rf);
}

/* check '|' {} count */
static int is_a_dot_escstring_node_record_nfcount(char *str)
{
	int ret = 0;
	char *p = NULL;
	p = str;
	while (*p) {
		/* skip esc. | */
		if (*p == '\\') {
			p++;
			if (*p == '\0') {
				break;
			}
			if (*p == '|') {
				p++;
				continue;
			}
		}
		/* watch the braces */
		if (*p == '{') {
			ret++;
		} else if (*p == '}') {
			ret++;
		} else if (*p == '|') {
			ret++;
		} else {	/* other char */
		}
		p++;
	}
	/* number of fields */
	return (ret);
}

/* check <> count */
static int is_a_dot_escstring_node_record_acount(char *str)
{
	int count = 0;
	char *p = NULL;
	p = str;
	while (*p) {
		/* chek for escaped <> */
		if (*p == '\\') {
			p++;
			if (*p == '\0') {
				break;
			}
			if ((*p == '<') || (*p == '>')) {
				p++;
				continue;
			}
		}
		if (*p == '<') {
			count++;
		} else if (*p == '>') {
			count--;
			if (count < 0) {	/* incorrect <> */
				break;
			}
		} else {	/* other char */
		}
		p++;
	}
	/* zero if matches <> else +/- */
	return (count);
}

/* check {} count */
static int is_a_dot_escstring_node_record_bracescount(char *str)
{
	int count = 0;
	char *p = NULL;
	p = str;
	while (*p) {
		/* chek for escaped {} */
		if (*p == '\\') {
			p++;
			if (*p == '\0') {
				break;
			}
			if ((*p == '{') || (*p == '}')) {
				p++;
				continue;
			}
		}
		if (*p == '{') {
			count++;
		} else if (*p == '}') {
			count--;
			if (count < 0) {	/* incorrect {} */
				break;
			}
		} else {	/* other char */
		}
		p++;
	}
	/* zero if matches {} else +/- */
	return (count);
}

/* print parsed data in the struct */
static void is_a_dot_escstring_node_record_rpr(struct fld *root)
{
	int ix = 0;
	struct fld *tmpf = NULL;

	ix = 0;
	tmpf = NULL;
	/* should not happen */
	if (root == NULL) {
		return;
	}
	if (root->f == NULL) {
		return;
	}
	for (;;) {
		if (root->f == NULL) {
			break;
		}
		tmpf = root->f[ix];
		if (tmpf == NULL) {
			break;
		}
		/* test print data or pointers */
		if (1) {
			printf("%s(root=%p): dir=%d \"%s\" ix=%d\n", __FUNCTION__, (void *)root, tmpf->dir, tmpf->label, ix);
		} else {
			printf("%s(root=%p): tmpf=%p ix=%d\n", __FUNCTION__, (void *)root, (void *)tmpf, ix);
		}
		fflush(stdout);
		ix++;
		is_a_dot_escstring_node_record_rpr(tmpf);
	}
	return;
}

/* free parsed data in the struct */
static void is_a_dot_escstring_node_record_freer(struct fld *root)
{
	int ix = 0;
	struct fld *tmpf = NULL;

	ix = 0;
	tmpf = NULL;

	/* should not happen */
	if (root == NULL) {
		return;
	}

	/* scan all ix */
	for (;;) {
		if (root->f == NULL) {
			break;
		}
		tmpf = root->f[ix];
		if (tmpf == NULL) {
			break;
		}
		is_a_dot_escstring_node_record_freer(tmpf);
		myfree(root->f[ix], __FUNCTION__, __LINE__);
		root->f[ix] = NULL;
		ix++;
	}
	if (root->f) {
		myfree(root->f, __FUNCTION__, __LINE__);
	}
	root->f = NULL;
	return;
}

/* esc chars have different meaning with shape=record */
static char *is_a_dot_escstring_node_record(char *str, struct unode *un)
{
	int nf = 0;
	struct fld *rf = NULL;
	char *s = NULL;

	parser_error_string = NULL;

	is_a_dot_escstring_node_record_errno = 0;

	un->f = NULL;

	/* check matching {} before parsing */
	if (is_a_dot_escstring_node_record_bracescount(str)) {
		/* no matching {} is a syntax error */
		is_a_dot_escstring_node_record_errno = 1;
	}

	/* check matching <> before parsing */
	if (is_a_dot_escstring_node_record_acount(str)) {
		/* no matching <> is a syntax error */
		is_a_dot_escstring_node_record_errno = 2;
	}

	if (is_a_dot_escstring_node_record_errno == 0) {
		/* how many fields in string using '|' or {} */
		nf = is_a_dot_escstring_node_record_nfcount(str);

		/* can be zero, the easiest */
		if (nf == 0) {
			/* get easy string */
		}

		/* two or more fields if here */
		recordstr = str;

		rf = is_a_dot_escstring_node_record_r(recordstr, un, 0 /* left-to-right */ );

		/* check if parse error */

		if (option_parsedebug || 0) {
			printf("%s(): %p for rf, rf->f=%p, errno=%d str=\"%s\"\n", __FUNCTION__, (void *)rf, (void *)rf->f,
			       is_a_dot_escstring_node_record_errno, str);
			fflush(stdout);
		}
		/* parse error can be */
		un->f = rf;
	}

	/* at parse error of the format string dot switches to "\N" nodename for record label */
	if (is_a_dot_escstring_node_record_errno) {
		switch (is_a_dot_escstring_node_record_errno) {
		case 0:	/* no error */
			parser_error_string = NULL;
			break;
		case 1:	/* no {} match */
			parser_error_string = "no matching '{' '}'";
			break;
		case 2:	/* no <> match */
			parser_error_string = "no matching '<' '>'";
			break;
		case 3:	/* unexpected end of string */
			parser_error_string = "unexpected end of format string";
			break;
		case 4:	/* string too long */
			parser_error_string = "format string is too long";
			break;
		case 5:	/* multiple port statements */
			parser_error_string = "multiple port statements";
			break;
		case 6:	/* multiple text alignment statements */
			parser_error_string = "multiple text align statements";
			break;
		case 7:	/* port tag not at begin of string */
			parser_error_string = "port tag not at begin";
			break;
		default:	/* should not happen */
			parser_error_string = "shouldnothappen";
			break;
		}
	}

	if (is_a_dot_escstring_node_record_errno) {
		/* here: free data, un->f=NULL message screen */
		printf("%s(): parse error %d %s for string \"%s\"\n", __FUNCTION__, is_a_dot_escstring_node_record_errno, s, str);
		is_a_dot_escstring_node_record_freer(un->f);
		if (un->f) {
			myfree(un->f, __FUNCTION__, __LINE__);
		}
		un->f = NULL;
		/* parser_error_string set above */
		fflush(stdout);
		return (NULL);
	}

	/* debug only print what is parsed */
	if (option_parsedebug || 0) {
		if (un->f) {
			is_a_dot_escstring_node_record_rpr(un->f);
			/* test only clear what is parsed */
			if (0) {
				is_a_dot_escstring_node_record_freer(un->f);
				myfree(un->f, __FUNCTION__, __LINE__);
				un->f = NULL;
			}
		}
	}
	fflush(stdout);
	return (uniqstring((char *)"record-shape"));
}

/* return new string with esc chars handled when used for label in a node.
 * the text label is divided in different areas using {} and | chars in dot.
 * the esc string effects depend on shape=record setting.
 * the chars \n, \l and \r do center/left/right align
 " the "\ " is a ' '
 * the "|" is a horizontal separator
 * the "||" is a empty vertical box
 * the "\|" is a '|'
 * the char \N does insert the name of the node
 * the char \G does insert the graph name
 * the char \E does insert edge name but in node label the chars TEH
 * the char \T does insert head of edge but in node label a char H
 * the char \H does insert head of edge but in node label a char H
 * the char \L does insert label but in node label a char L
 * the char \\ is translated as single char \
 * the char \{ is translated into {
 * the char \} is translated into }
 * the char \| is translated into |
 * other char \char is translated into char itself and \ is skipped
 * to add plain {}| in the format use it with a backslash
 * gcc data is using this format string to have the lines in boxes left aligned.
 * the last \n is deleted to save space in the drawing of the node.
 * a single '\' is returned as such.
 * {} and <> count must match and graphviz does not check for that
 * dot allows '{' in a port but not '}' or '|'
 * dot generates wrong images without warning at few field syntax errors
 */
char *is_a_dot_escstring_node(char *str, struct unode *un)
{
	int i = 0;
	int maxc = 0;
	char *buf = NULL;
	char *p = NULL;
	char *p2 = NULL;
	char c = 0;
	char *q = NULL;
	char *q2 = NULL;
	/* check string */
	if (str == NULL) {
		printf("%s(): str is NULL shouldnothappen\n", __FUNCTION__);
		fflush(stdout);
		return (NULL);
	}
	if (strlen(str) == 0) {
		return (uniqstring((char *)"<empty>"));
	}
	/* if only one char the just return, cannot have chars to unesc. */
	if (strlen(str) == 1) {
		/* make sure '\n' becomes a space */
		if (*str == '\n') {
			return (uniqstring(" "));
		}
		return (str);
	}

	/* esc chars have different meaning with shape=record */
	if (un->bitflags.shape == NSHAPE_RECORD) {
		p = is_a_dot_escstring_node_record(str, un);
		if (p == NULL) {
			/* parse error set in
			   parser_error_string */
			return (NULL);
		}
		return (uniqstring((char *)"<record-label>"));
	}

	/* check for special control chars backslash and braces */
	p = strchr(str, '\\');
	p2 = strchr(str, 0x7b);
	if (p2 == NULL) {
		p2 = strchr(str, 0x7d);
	}
	/* check */
	if ((p == NULL) && (p2 == NULL)) {
		/* string without control chars and no need to modify */
		return (str);
	}

	/* how long can the string be from lexer */
	maxc = dotlex_max_strlen();
	/* buffer for new text */
	buf = mymalloc(maxc, __FUNCTION__, __LINE__);
	/* scan the string */
	i = 0;
	p = str;
	while ((*p)) {
		c = (*p);
		if (c == 0x7b) {
			/* start of sequence of special format string with brace open */
			p = p + 1;
			continue;
		}
		if (c == 0x7d) {
			/* end of sequence of special format string with brace close */
			p = p + 1;
			continue;
		}
		if (c == '|') {
			/* start of new text area with vertical bar */
			p = p + 1;
			continue;
		}
		/* check for esc string */
		if (c == '\\') {
			p = p + 1;
			c = (*p);
			/* check the escaped char */
			if (c == 0) {
				/* end-of-string */
				break;
			} else if (c == 'n') {
				/* center text */
				/* change in newline */
				c = '\n';
			} else if (c == 'l') {
				/* left align text */
				/* change in newline */
				c = '\n';
			} else if (c == 'r') {
				/* right aling text */
				/* change in newline */
				c = '\n';
			} else if (c == 'N') {
				/* name of node inserted */
				q = un->name;
				if (q) {
					if ((int)(strlen(q) + i) >= (maxc - 1)) {
						/* node name is too long for buffer */
						break;
					}
					/* set q to end of string in buf */
					q = buf;
					q = (q + i);
					q = strcat(buf, un->name);
					i = (i + (int)strlen(un->name));
					c = 0;
				} else {
					/* if no node name set a char N */
					c = 'N';
				}
			} else if (c == 'L') {
				/* name of label inserted but in node label a L */
				c = 'L';
			} else if (c == 'E') {
				/* name of edge inserted */
				/* the char \E does insert edge name but in node label the chars "TEH". to update. */
				c = 'T';
				buf[i] = c;
				i = i + 1;
				/* check end of buffer */
				if (i >= (maxc - 1)) {
					break;
				}
				c = 'E';
				buf[i] = c;
				i = i + 1;
				/* check end of buffer */
				if (i >= (maxc - 1)) {
					break;
				}
				c = 'H';
			} else if (c == 'H') {
				/* head of edge inserted */
				/* char \H does insert head of edge but in node label a char H */
				c = 'H';
			} else if (c == 'T') {
				/* tail of edge inserted */
				/* char \T does insert head of edge but in node label a char T */
				c = 'T';
			} else if (c == 'G') {
				/* set name of graph, in root graph the name of file
				 * without extension and no numbers in the name,
				 * in a subgraph the same filename. to update.
				 */
				/* different done here setting the name of root or subgraph */
				if (un->rootedon) {
					q2 = un->rootedon->name;
				} else {
					q2 = "root";
				}
				if ((int)(strlen(q2) + i) >= (maxc - 1)) {
					/* node name is too long for buffer */
					break;
				}
				/* set q to end of string in buf */
				q = buf;
				q = (q + i);
				q = strcat(buf, q2);
				i = (i + (int)strlen(q2));
				c = 0;
			} else {
				/* other \ char literally copied */
				c = (*p);
			}
		}

		/* insert char if any */
		if (c) {
			buf[i] = c;
			i = i + 1;
			/* check end of buffer */
			if (i >= (maxc - 1)) {
				break;
			}
		}
		/* next char from string */
		p = p + 1;
	}
	/* the last \n is deleted to save space in the drawing of the node. */
	i = (int)strlen(buf);
	if (i > 1) {
		/* change \n into 0x00 */
		if (buf[(i - 1)] == '\n') {
			buf[(i - 1)] = 0x00;
		}
	} else {
		/* 1 char in buffer, change \n into ' ' */
		if (buf[0] == '\n') {
			/* do not change in 0 because "" is used for dummy nodes */
			buf[0] = ' ';
			buf[1] = '\0';
		}
	}
	/* create fresh copy of new string */
	if (strlen(buf) == 0) {
		buf[0] = ' ';
		buf[1] = '\0';
	}
	p = uniqstring(buf);
	myfree(buf, __FUNCTION__, __LINE__);
	/* return the new formatted and buffered string */
	if (option_parsedebug || 0) {
		printf("%s(): result-string \"%s\"\n", __FUNCTION__, p);
	}
	return (p);
}

/* */
static void dot_clrv(splay_tree_value v)
{
	struct des *ptr = NULL;
	ptr = ((struct des *)v);
	if (ptr) {
		myfree((void *)ptr, __FUNCTION__, __LINE__);
	}
	return;
}

/* */
static void dot_desclear(void)
{
	if (destree == (splay_tree) 0) {
		ndes = 0;
		return;
	}
	destree = splay_tree_delete(destree);
	ndes = 0;
	return;
}

/* */
void dot_desadd(char *fname, struct usubg *fsubg, char *tname, struct usubg *tsubg, int code)
{
	struct des *newdes = NULL;
	if (destree == (splay_tree) 0) {
		/* key is int number, value a struct to free() at delete */
		destree = splay_tree_new(splay_tree_compare_ints, NULL, dot_clrv);
	}

	newdes = mymalloc(sizeof(struct des), __FUNCTION__, __LINE__);
	switch (code) {
	case DES_N_S:		/* from is nodename, to is subgraph */
		newdes->f.name = fname;
		newdes->t.subg = tsubg;
		break;
	case DES_S_S:
		newdes->f.subg = fsubg;
		newdes->t.subg = tsubg;
		break;
	case DES_N_N:
		newdes->f.name = fname;
		newdes->t.name = tname;
		break;
	case DES_S_N:		/* from is subgraph, to is nodename */
		newdes->f.subg = fsubg;
		newdes->t.name = tname;
		break;
	default:
		myfree(newdes, __FUNCTION__, __LINE__);
		return;		/* should not happen */
		break;
	}

	/* type of data in value */
	newdes->typ = code;

	splay_tree_insert((splay_tree) destree, (splay_tree_key) ndes, (splay_tree_value) newdes);

	/* to next key */
	ndes = ndes + 1;

	return;
}

/* */
static void dot_desexpand(void)
{
	int status = 0;

	if (destree == (splay_tree) 0) {
		return;
	}

	if (splay_tree_has_data(destree)) {
		/* */
		status = splay_tree_foreach((splay_tree) destree, dot_desexpand1, NULL);
		if (status) {
			/* error */
		}
	}

	return;
}

/* */
static int dot_desexpand1(splay_tree_node n, void *data)
{
	struct des *nd = NULL;
	int nr = 0;
	int code = 0;

	if (data) {
	}

	nr = (int)n->key;
	nd = (struct des *)n->value;

	code = nd->typ;

	switch (code) {
	case DES_N_S:		/* from is nodename, to is subgraph */
		dot_desexpand1ns(nd->f.name, nd->t.subg);
		break;
	case DES_S_S:
		dot_desexpand1ss(nd->f.subg, nd->t.subg);
		break;
	case DES_N_N:
		dot_desexpand1e(nd->f.name, nd->t.name);
		break;
	case DES_S_N:		/* from is subgraph, to is nodename */
		dot_desexpand1sn(nd->f.subg, nd->t.name);
		break;
	default:
		/* should not happen */
		return (0);
		break;
	}
	if (nr) {
	}
	return (0);
}

/* to update with few edge attr as color, style, thickness, label */
static void dot_desexpand1e(char *fname, char *tname)
{
	struct unode *fnode = NULL;
	struct unode *tnode = NULL;
	struct uedge *ue = NULL;

	fnode = uniqnode(fname);

	if (fnode == (struct unode *)0) {
		printf("%s(): fixme nil fnode\n", __FUNCTION__);
		fflush(stdout);
		return;
	}

	tnode = uniqnode(tname);

	if (tnode == (struct unode *)0) {
		printf("%s(): fixme nil tnode\n", __FUNCTION__);
		fflush(stdout);
		return;
	}

	/* create new edge */
	ue = uedge_new(fnode, tnode);

	/* rooted on from node */
	ue->rootedon = fnode->rootedon;

	/* set defaults */
	set_edge_defaults(ue, ue->rootedon);

	/* add as parsed edge */
	parsedel_add(ue);

	return;
}

/* */
static void dot_desexpand1ss(struct usubg *fsubg, struct usubg *tsubg)
{
	struct unode *un = (struct unode *)0;
	struct usubg *sg = (struct usubg *)0;
	splay_tree_node spn = (splay_tree_node) 0;

	if (splay_tree_has_data(fsubg->sp_nl)) {

		spn = splay_tree_min(fsubg->sp_nl);

		while (spn) {
			un = (struct unode *)spn->value;
			dot_desexpand1ns(un->name, tsubg);
			spn = splay_tree_successor(fsubg->sp_nl, spn->key);
		}
	}

	if (splay_tree_has_data(fsubg->sp_sg)) {

		spn = splay_tree_min(fsubg->sp_sg);

		while (spn) {
			sg = (struct usubg *)spn->value;

			dot_desexpand1ss(sg, tsubg);

			spn = splay_tree_successor(fsubg->sp_sg, spn->key);
		}
	}

	return;
}

/* */
static void dot_desexpand1ns(char *fname, struct usubg *tsubg)
{
	struct usubg *sg = (struct usubg *)0;
	struct unode *un = (struct unode *)0;
	splay_tree_node spn = (splay_tree_node) 0;
	if (splay_tree_has_data(tsubg->sp_nl)) {

		spn = splay_tree_min(tsubg->sp_nl);
		while (spn) {
			un = (struct unode *)spn->value;
			dot_desexpand1e(fname, un->name);
			spn = splay_tree_successor(tsubg->sp_nl, spn->key);
		}
	}

	if (splay_tree_has_data(tsubg->sp_sg)) {

		spn = splay_tree_min(tsubg->sp_sg);
		while (spn) {
			sg = (struct usubg *)spn->value;
			dot_desexpand1ns(fname, sg);
			spn = splay_tree_successor(tsubg->sp_sg, spn->key);
		}
	}

	return;
}

/* */
static void dot_desexpand1sn(struct usubg *fsubg, char *tname)
{
	struct usubg *sg = (struct usubg *)0;
	struct unode *un = (struct unode *)0;
	splay_tree_node spn = (splay_tree_node) 0;
	if (splay_tree_has_data(fsubg->sp_nl)) {

		spn = splay_tree_min(fsubg->sp_nl);
		while (spn) {
			un = (struct unode *)spn->value;
			dot_desexpand1e(un->name, tname);
			spn = splay_tree_successor(fsubg->sp_nl, spn->key);
		}
	}

	if (splay_tree_has_data(fsubg->sp_sg)) {

		spn = splay_tree_min(fsubg->sp_sg);
		while (spn) {
			sg = (struct usubg *)spn->value;
			dot_desexpand1sn(sg, tname);
			spn = splay_tree_successor(fsubg->sp_sg, spn->key);
		}
	}

	return;
}

/* handle optional ';' at end of statement */
static void dot_opt_semicomma(void)
{
	token = dot_lex();
	if (token == DOT_SEMIC) {
		/* oke and skip the ; */
	} else {
		/* push back */
		dot_unlex();
	}
	return;
}

/* */
void parsedot_clear(void)
{
	dot_desclear();
	/* no pending edge data */
	dot_pe_clear();
	dot_pe_free();
	/* */
	dotlexreset();
	/* root graph default edge line color black */
	ed_color = 0;
	ed_fcolor = 0;
	ed_ffontname = NULL;
	ed_thickness = 0;
	ed_label = NULL;
	ed_style = ESTYLE_NORMAL;
	ed_fontsize = NULL;
	ed_cs = 0;
	nd_url = NULL;
	nd_bcolor = 0;		/* black */
	nd_cs = 0;
	nd_fillcolor = 0x00ffffff;	/* white */
	nd_label = NULL;
	nd_labeltype = 0;
	return;
}

/* get current lexer line number */
int get_dotlex_lineno(void)
{
	int ret = 0;
	/* in dot.l */
	ret = dotlex_lineno();
	return (ret);
}

/* */
int parsedot(char *fname, FILE * fstream)
{

	if (option_parsedebug) {
		printf("%s(): switching to dotparser for file %s\n", __FUNCTION__, fname);
		fflush(stdout);
	}

	/* start at begin of file */
	rewind(fstream);

	/* this is a graphviz format */
	parsedformat = 1;

	dotlexinit(fstream, fname, option_parsedebug);

	/* set pre-compiled defaults for defaults */
	set_graph_default_fdefaults();
	set_node_default_fdefaults();
	set_edge_default_fdefaults();

	/* no error string */
	parser_error_string = NULL;

	/* tokens parsed */
	token = 0;
	token1 = 0;
	token1_laststring = (char *)0;
	token1_lastid = (char *)0;
	token1_lastnum = (char *)0;
	token1_lastchar = (char *)0;
	token1_lasthtml = (char *)0;
	prev_token = 0;
	prev_laststring = (char *)0;
	prev_lastid = (char *)0;
	prev_lastnum = (char *)0;
	prev_lasthtml = (char *)0;
	prev_lastchar = (char *)0;

	/* number of compound and subgraph statements */
	ncompound = 0;
	nsubgraph = 0;

	/* root graph default edge line color black */
	ed_color = 0;

	/* root graph default edge font text color black */
	ed_fcolor = 0;

	/* root graph edge label font name */
	ed_ffontname = NULL;

	/* root graph edge line thickness */
	ed_thickness = 0;

	/* root graph edge label */
	ed_label = NULL;

	/* root graph edge style */
	ed_style = ESTYLE_NORMAL;

	/* root graph edge label font size */
	ed_fontsize = NULL;

	/* root graph node url */
	nd_url = NULL;

	/* root graph node border color */
	nd_bcolor = 0;		/* black */

	/* color scheme */
	nd_cs = 0;

	/* node fill color */
	nd_fillcolor = 0x00ffffff;

	/* node label */
	nd_label = NULL;
	nd_labeltype = 0;
	ed_cs = 0;

	/* no pending edge data */
	dot_pe_clear();
	dot_pe_free();

	/* lex first token */
	token = dot_lex();

	/* opt utf-8 code at start */
	if (token == DOT_UTF8BOM) {
		token = dot_lex();
	}

	/* optional strict or STRICT keyword */
	if (token == DOT_STRICT) {
		/* mark the strict mode here */
		token = dot_lex();
	}

	/* graph "name" {} or digraph "name" {} */
	if (token == DOT_GRAPH) {
		token = dot_lex();
	} else if (token == DOT_DIGRAPH) {
		token = dot_lex();
	} else {
		/* no pending edge data */
		dot_desclear();
		dot_pe_clear();
		dot_pe_free();
		parser_error_string = "expected graph or digraph at start of graph";
		return (1);
	}

	/* get optional name of graph */
	token = dot_lex();

	if (token == DOT_STRING) {
		token = dot_lex();
	} else if (token == DOT_ID) {
		token = dot_lex();
	} else if (token == DOT_CHAR) {
		token = dot_lex();
	} else {
		/* empty graphname */
	}

	/* at start of main body of graph with brace open */
	if (token != DOT_BO) {
		/* no pending edge data */
		dot_desclear();
		dot_pe_clear();
		dot_pe_free();
		parser_error_string = "expected brace open at start of graph";
		return (1);
	}

	parser_error_string = NULL;

	/* parse rest of dot file and start at root graph */
	if (parsedot2((struct usubg *)0) == 0) {

		/* at error there should be error string */
		if (parser_error_string == NULL) {
			parser_error_string = "parse error in body of graph or missing a closing brace";
		}

		if (option_parsedebug) {
			printf("%s(): parse error status <>0 from parsedot2() with error \"%s\"\n", __FUNCTION__,
			       parser_error_string);
			fflush(stdout);
		}

		/* no pending edge data */
		dot_desclear();
		dot_pe_clear();
		dot_pe_free();

		/* error parsed */
		return (1);
	}

	if (option_parsedebug) {
		printf("%s(): parse status oke now doing rest of parse\n", __FUNCTION__);
		fflush(stdout);
	}

	/* tmp create work nl and el */

	/* count selfedges at nodes */
	markselfedges();

	/* indicate single nodes */
	marksinglenodes();

	/* input statistics */
	input_stats();

	/* build working lists */
	folding_build_wl();

	/* tmp create node/edge list in subgraphs needed for expand */
	rebuild_subg_nel();

	/* parsed oke if here and expand {}-> edges */
	dot_desexpand();

	/* clear folding bits */
	clear_subg_nel();

	/* all subgraphs initially folded */
	folding_all(1);

	/* no pending edge data */
	dot_desclear();
	dot_pe_clear();
	dot_pe_free();

	/* parsed oke status */
	return (0);
}

/* parse body of graph or subgraph or compound */
int parsedot2(struct usubg *subg)
{

	/* keep on parsing elements in root or sub graph */
	for (;;) {

		/* stop if error status is set */
		if (parser_error_string || token == 0) {
			return (0);
		}

		/* get token to parse */
		token = dot_lex();

		if (option_parsedebug) {
			printf("%s(): token is 0x%x %d\n", __FUNCTION__, (int)token, (int)token);
			fflush(stdout);
		}

		/* check for end-of-file */
		if (token == EOF || token == 0) {
			/* easier */
			parser_error_string = "un-expected end-end-of-file";
			return (0);
		}

		/* check for close brace */
		if (token == DOT_BC) {
			if (option_parsedebug) {
				printf("%s(): token is close-brace\n", __FUNCTION__);
				fflush(stdout);
			}

			/* check if parsing error happened */
			if (parser_error_string) {
				return (0);
			}

			/* end of graph or compound */
			if (subg) {

				/* end of compound */
				if (option_parsedebug) {
					printf("%s(): end-of-compound-or-subgraph\n", __FUNCTION__);
				}

				return (1);
			} else {
				/* normal end of graph */
				if (option_parsedebug) {
					printf("%s(): end-of-graph\n", __FUNCTION__);
				}
				return (1);
			}
		}

		if (token == DOT_SEMIC) {
			/* stand alone ; */
			if (parser_error_string) {
				return (0);
			}
			continue;
		}

		if (is_a_dotcedge(subg)) {
			/* -- or -> between subgraphs or compounds */
			if (parser_error_string) {
				return (0);
			}
			continue;
		}

		if (is_a_dotcompoundgraph(subg)) {
			/* { ... } */
			if (parser_error_string) {
				return (0);
			}
			continue;
		}

		if (token == DOT_HTML) {
			if (option_parsedebug) {
				printf("%s(): token is dot_html\n", __FUNCTION__);
				fflush(stdout);
			}

			if (subg) {
				parser_error_string = "unexpected html-string in subgraph";
			} else {
				parser_error_string = "unexpected html-string in root graph";
			}
			return (0);
		}

		if (token == DOT_STRING) {
			if (option_parsedebug) {
				printf("%s(): token is dot_string\n", __FUNCTION__);
				fflush(stdout);
			}

			if (is_a_dotne(subg)) {
				/* node or edge */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (subg) {
				parser_error_string = "unexpected string in subgraph";
			} else {
				parser_error_string = "unexpected string in root graph";
			}
			return (0);
		}

		if (token == DOT_NUM) {
			if (option_parsedebug) {
				printf("%s(): token is dot_num\n", __FUNCTION__);
				fflush(stdout);
			}

			/* number */
			if (is_a_dotne(subg)) {
				/* node or edge */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (subg) {
				parser_error_string = "unexpected number in subgraph";
			} else {
				parser_error_string = "unexpected number in root graph";
			}
			return (0);
		}

		/* 92 graph settings keywords parsed as identifiers
		 * Damping K URL aspect bb bgcolor center charset
		 * clusterrank color colorscheme comment compound
		 * concentrate defaultdist dim dimen diredgeconstraints
		 * dpi epsilon esep fillcolor fontcolor fontname
		 * fontnames fontpath fontsize id label labeljust
		 * labelloc landscape layers layersep layout levels
		 * levelsgap lheight lp lwidth margin maxiter mclimit
		 * mindist mode model mosek nodesep nojustify normalize
		 * nslimit nslimit1 ordering orientation outputorder
		 * overlap overlap_scaling pack packmode pad page pagedir
		 * pencolor penwidth peripheries quadtree quantum rank
		 * rankdir ranksep ratio remincross repulsiveforce
		 * resolution root rotate searchsize sep showboxes
		 * size smoothing sortv splines start style stylesheet
		 * target tooltip truecolor viewport voro_margin
		 * (and there are more then this)
		 */

		if (token == DOT_CHAR) {
			if (option_parsedebug) {
				printf("%s(): token is dot_char\n", __FUNCTION__);
				fflush(stdout);
			}

			/* single char keywords */
			if (is_a_dotk(subg)) {
				/* K=fp-num */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			} else {
				/* char */
				if (is_a_dotne(subg)) {
					/* node or edge */
					dot_opt_semicomma();
					dot_pe_clear();
					if (parser_error_string) {
						return (0);
					}
					continue;
				}

				/* other char, possible syntax error */

				return (0);
			}
		}

		/* id or subgraph, node, edge, graph-default */
		if (token == DOT_SUBGRAPH) {
			if (option_parsedebug) {
				printf("%s(): trying subgraph\n", __FUNCTION__);
				fflush(stdout);
			}

			if (is_a_dotsubgraph(subg)) {
				/* subgraph opt-name { ... } */
				dot_opt_semicomma();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

		}

		if (option_parsedebug) {
			printf("%s(): trying node default\n", __FUNCTION__);
			fflush(stdout);
		}

		if (token == DOT_NODE) {

			if (is_a_dotnodedefault(subg)) {
				/* node[options] */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}
		}

		if (option_parsedebug) {
			printf("%s(): trying edge default\n", __FUNCTION__);
			fflush(stdout);
		}

		if (token == DOT_EDGE) {
			if (is_a_dotedgedefault(subg)) {
				/* edge[options] */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}
		}

		if (option_parsedebug) {
			printf("%s(): trying graph default\n", __FUNCTION__);
			fflush(stdout);
		}

		if (is_a_dotgraphdefault(subg)) {
			/* graph[options] */
			dot_opt_semicomma();
			dot_pe_clear();
			if (parser_error_string) {
				return (0);
			}
			continue;
		}

		/* token is identifier */
		if (token == DOT_ID) {
			if (option_parsedebug) {
				printf("%s(): token is dot_id\n", __FUNCTION__);
				fflush(stdout);
			}

			if (option_parsedebug) {
				printf("%s(): trying background\n", __FUNCTION__);
				fflush(stdout);
			}

			if (is_a_dot_background(subg)) {
				/* _background=color in xdot format */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotbb(subg)) {
				/* bb=rect */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotbgcolor(subg)) {
				/* bgcolor=color */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotcenter(subg)) {
				/* center=true/false */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotcharset(subg)) {
				/* charset="name" */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotclusterrank(subg)) {
				/* clusterrank=local/global/none */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
			}

			if (is_a_dotcolor(subg)) {
				/* color=name */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotcolorscheme(subg)) {
				/* colorscheme=name */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotcomment(subg)) {
				/* comment=string */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotcompound(subg)) {
				/* compound=true/false */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotconcentrate(subg)) {
				/* concentrate=true/false */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotdamping(subg)) {
				/* Damping=1.0 */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotdefaultdist(subg)) {
				/* defaultdist=1.0 */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotdim(subg)) {
				/* dim=1 int */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotdimen(subg)) {
				/* dimen=1 int */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotdiredgeconstraints(subg)) {
				/* diredgeconstraints=string */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotdpi(subg)) {
				/* dpi=fp-number */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotepsilon(subg)) {
				/* epsilon=fp-number */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotesep(subg)) {
				/* esep=fp-number */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotfillcolor(subg)) {
				/* fillcolor=color */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotfontcolor(subg)) {
				/* fontcolor=color */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotfontname(subg)) {
				/* fontname=name */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotfontnames(subg)) {
				/* fontnames=names */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotfontpath(subg)) {
				/* fontpath=string */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotfontsize(subg)) {
				/* fontsize=fp-num */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotforcelabels(subg)) {
				/* forcelabels=true/false */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotgradientangle(subg)) {
				/* gradientangle=int */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dothref(subg)) {
				/* href=url */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotid(subg)) {
				/* id=string */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotimagepath(subg)) {
				/* imagepath=string */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotinputscale(subg)) {
				/* inputscale=fp-num */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotlabel(subg)) {
				/* label=string */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotlabel_scheme(subg)) {
				/* label_scheme=int */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotlabeljust(subg)) {
				/* labeljust=string */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotlabelloc(subg)) {
				/* labelloc=string */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotlandscape(subg)) {
				/* landscape=true/false */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotlayerlistsep(subg)) {
				/* layerlistsep=string */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotlayers(subg)) {
				/* layers=string */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotlayerselect(subg)) {
				/* layerselect=string */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotlayersep(subg)) {
				/* layersep=string */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotlayout(subg)) {
				/* layout=string */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotlevels(subg)) {
				/* levels=int */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotlevelsgap(subg)) {
				/* levelsgap=fp-num */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotlheight(subg)) {
				/* lheight=fp-num */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotlp(subg)) {
				/* lp=point as in "%f,%f(!)?" also in 3d */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotlwidth(subg)) {
				/* lwidth=fp-num */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotmargin(subg)) {
				/* margin=fp-num */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotmaxiter(subg)) {
				/* maxiter=int-num */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotmclimit(subg)) {
				/* mclimit=fp-num */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotmindist(subg)) {
				/* mindist=fp-num */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotmode(subg)) {
				/* mode=string */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotmodel(subg)) {
				/* model=string */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotmosek(subg)) {
				/* mosek=true/false */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotnodesep(subg)) {
				/* nodesep=fp-num */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotnojustify(subg)) {
				/* nojustify=true/false */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotnormalize(subg)) {
				/* normalize=fp-num */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotnotranslate(subg)) {
				/* notranslate=true/false */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotnslimit(subg)) {
				/* nslimit=fp-num */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotnslimit1(subg)) {
				/* nslimit1=fp-num */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotordering(subg)) {
				/* ordering=string */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotorientation(subg)) {
				/* orientation=string */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotoutputorder(subg)) {
				/* outputorder=string */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotoverlap(subg)) {
				/* overlap=string */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotoverlap_scaling(subg)) {
				/* overlap_scaling=fp-num */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotoverlap_shrink(subg)) {
				/* overlap_shrink=true/false */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotpack(subg)) {
				/* pack=true/false */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotpackmode(subg)) {
				/* packmode=string */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotpad(subg)) {
				/* pad=fp-num */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotpage(subg)) {
				/* page=fp-num */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotpagedir(subg)) {
				/* pagedir=string BL,BR etc */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotpencolor(subg)) {
				/* pencolor=string */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotpenwidth(subg)) {
				/* penwidth=fp-num */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotquadtree(subg)) {
				/* quadtree=string normal/fast/none */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotquantum(subg)) {
				/* quantum=fp-num */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotrankdir(subg)) {
				/* rankdir=string */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotrank(subg)) {
				/* rank=string */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotranksep(subg)) {
				/* ranksep=fp-num */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotratio(subg)) {
				/* ratio=fp-num */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotremincross(subg)) {
				/* remincross=true/false */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotrepulsiveforce(subg)) {
				/* repulsiveforce=fp-num */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotresolution(subg)) {
				/* resolution=fp-num */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotroot(subg)) {
				/* root=string */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotrotate(subg)) {
				/* rotate=int */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotrotation(subg)) {
				/* rotate=fp-num */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotsamplepoints(subg)) {
				/* samplepoints */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotscale(subg)) {
				/* scale=fp-num */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotsearchsize(subg)) {
				/* searchsize=int */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotsep(subg)) {
				/* sep=fp-num or pointf */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotshowboxes(subg)) {
				/* showboxes=int */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotsize(subg)) {
				/* sep=fp-num */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotsmoothing(subg)) {
				/* smoothing=string */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotsortv(subg)) {
				/* sortv=int */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotsplines(subg)) {
				/* splines=true/false */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotstart(subg)) {
				/* start=string */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotstyle(subg)) {
				/* style=string */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotstylesheet(subg)) {
				/* stylesheet=string */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dottarget(subg)) {
				/* target=string */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dottruecolor(subg)) {
				/* truecolor=true/false */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_doturl(subg)) {
				/* url=string */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotviewport(subg)) {
				/* viewport=string "%lf,%lf,%lf,%lf,%lf" */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (is_a_dotvoro_margin(subg)) {
				/* voro_margin=fp-num */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (option_parsedebug) {
				printf("%s(): trying xdotversion (last one)\n", __FUNCTION__);
				fflush(stdout);
			}

			if (is_a_dotxdotversion(subg)) {
				/* xdotversion=string */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			/* all graph options tried by now */

			if (option_parsedebug) {
				printf("%s(): trying dotne\n", __FUNCTION__);
				fflush(stdout);
			}

			if (is_a_dotne(subg)) {
				/* assumed node or edge definition */
				dot_opt_semicomma();
				dot_pe_clear();
				if (parser_error_string) {
					return (0);
				}
				continue;
			}

			if (option_parsedebug) {
				printf("%s(): unknown statement trying to skip bah=nonsense statement\n", __FUNCTION__);
				fflush(stdout);
			}

			/* skip boo=bah */
			token = dot_lex();
			if (token == DOT_IS) {
				/* accept anything after the '=' */
				token = dot_lex();
				/* todo option here to check for id/string/char/number but not '=', ',' etc. which is not accepted by dot */
				dot_opt_semicomma();
				dot_pe_clear();
				parser_error_string = NULL;
				continue;
			}

			dot_opt_semicomma();
			dot_pe_clear();
			/* */
			if (subg) {
				parser_error_string = "unknown statement in subgraph";
			} else {
				parser_error_string = "unknown statement in root graph";
			}
			return (0);
			/* 0) end-of-graph-options */
		} else {

			dot_opt_semicomma();
			dot_pe_clear();
			/* keep error sting if any */
			if (parser_error_string == NULL) {
				/* error status */
				if (subg) {
					parser_error_string = "unknown statement in subgraph";
				} else {
					parser_error_string = "unknown statement in root graph";
				}
			}
			return (0);
		}
	}

	/* oke status */
	return (1);
}

/* set pre-compiled defaults for graph defaults */
static void set_graph_default_fdefaults(void)
{
	return;
}

/* set pre-compiled defaults for defaults */
static void set_node_default_fdefaults(void)
{
	/* node default shape "node[shape="name"]" */
	nd_shape = 0;
	/* node url */
	nd_url = NULL;
	/* border color */
	nd_bcolor = 0;
	/* color scheme */
	nd_cs = 0;
	/* node fill color */
	nd_fillcolor = 0x00ffffff;	/* white */
	/* node label */
	nd_label = NULL;
	nd_labeltype = 0;
	/* node default fontsize "node[fontsize=10]" NULL is gtk default */
	nd_fontsize = NULL;
	/* node default fontname "node[fontname=name]" NULL is gtk default */
	nd_fontname = NULL;
	/* style=filled */
	nd_stylefilled = 0;
	return;
}

/* set defaults for a fresh node */
void set_node_defaults(struct unode *un, struct usubg *subg)
{
	if (subg == NULL) {
		/* node default shape "node[shape="name"]" */
		un->bitflags.shape = nd_shape;
		/* url of node */
		un->url = nd_url;
		/* border color */
		un->bcolor = nd_bcolor;
		/* color scheme */
		un->bitflags.cscheme = nd_cs;
		/* fillcolor */
		un->fillcolor = nd_fillcolor;
		/* label */
		un->label = nd_label;
		un->labeltype = nd_labeltype;
		/* node default fontname "node[fontname=name]" */
		un->fontname = nd_fontname;
		/* node default fontsize "node[fontsize=10]" */
		un->fontsize = nd_fontsize;
		/* style=filled */
		if (nd_stylefilled) {
			un->bitflags2.stylefilled = 1;
		} else {
			un->bitflags2.stylefilled = 0;
		}
	} else {
		un->bitflags.shape = subg->nd_shape;
		un->url = subg->nd_url;
		un->bcolor = subg->nd_bcolor;
		un->bitflags.cscheme = subg->nd_cs;
		un->fillcolor = subg->nd_fillcolor;
		un->label = subg->label;
		un->labeltype = subg->nd_labeltype;
		un->fontname = subg->nd_fontname;
		un->fontsize = subg->nd_fontsize;
		if (subg->nd_stylefilled) {
			un->bitflags2.stylefilled = 1;
		} else {
			un->bitflags2.stylefilled = 0;
		}
	}
	return;
}

/* set pre-compiled defaults for defaults */
static void set_edge_default_fdefaults(void)
{
	return;
}

void set_edge_defaults(struct uedge *ue, struct usubg *subg)
{
	/* set edge defaults first */
	if (subg == NULL) {
		/* default edge line color */
		ue->color = ed_color;
		/* font color for edge label */
		ue->textcolor = ed_fcolor;
		/* font for label */
		ue->fontname = ed_ffontname;
		/* edge line thickness */
		ue->bitflags.thickness = ed_thickness;
		/* label */
		ue->label = ed_label;
		/* line style */
		ue->bitflags.style = ed_style;
		/* fontsize */
		ue->fontsize = ed_fontsize;
		/* color scheme */
		ue->bitflags.cscheme = ed_cs;
	} else {
		/* default edge line color */
		ue->color = subg->ed_color;
		/* font color for edge label */
		ue->textcolor = subg->ed_fcolor;
		/* font for label */
		ue->fontname = subg->ed_ffontname;
		/* edge line thickness */
		ue->bitflags.thickness = subg->ed_thickness;
		/* label */
		ue->label = subg->ed_label;
		/* line style */
		ue->bitflags.style = subg->ed_style;
		/* fontsize */
		ue->fontsize = subg->ed_fontsize;
		/* color scheme */
		ue->bitflags.cscheme = subg->ed_cs;
	}
	return;
}

/* copy node/edge defaults from rootedon */
void set_subg_defaults(struct usubg *rootedon, struct usubg *newsg)
{
	if (rootedon == NULL) {
		/* node shape */
		newsg->nd_shape = nd_shape;
		/* url of node */
		newsg->nd_url = nd_url;
		/* border color */
		newsg->nd_bcolor = nd_bcolor;
		/* color scheme */
		newsg->nd_cs = nd_cs;
		/* fillcolor */
		newsg->nd_fillcolor = nd_fillcolor;
		/* label */
		newsg->nd_label = nd_label;
		newsg->nd_labeltype = nd_labeltype;
		/* fontname */
		newsg->nd_fontname = nd_fontname;
		/* fontsize */
		newsg->nd_fontsize = nd_fontsize;
		/* style filled */
		newsg->nd_stylefilled = nd_stylefilled;
		/* default edge line color */
		newsg->ed_color = ed_color;
		/* font color for edge label */
		newsg->ed_fcolor = ed_fcolor;
		/* font for label */
		newsg->ed_ffontname = ed_ffontname;
		/* edge line thickness */
		newsg->ed_thickness = ed_thickness;
		/* label */
		newsg->ed_label = ed_label;
		/* line style */
		newsg->ed_style = ed_style;
		/* fontsize */
		newsg->ed_fontsize = ed_fontsize;
		/* color scheme */
		newsg->ed_cs = ed_cs;
	} else {
		/* node shape */
		newsg->nd_shape = rootedon->nd_shape;
		/* url of node */
		newsg->nd_url = rootedon->nd_url;
		/* border color */
		newsg->nd_bcolor = rootedon->nd_bcolor;
		/* color scheme */
		newsg->nd_cs = rootedon->nd_cs;
		/* fillcolor */
		newsg->nd_fillcolor = rootedon->nd_fillcolor;
		/* label */
		newsg->nd_label = rootedon->nd_label;
		newsg->nd_labeltype = rootedon->nd_labeltype;
		/* fontname */
		newsg->nd_fontname = rootedon->nd_fontname;
		/* fontsize */
		newsg->nd_fontsize = rootedon->nd_fontsize;
		/* style filled */
		newsg->nd_stylefilled = rootedon->nd_stylefilled;
		/* default edge line color */
		newsg->ed_color = rootedon->ed_color;
		/* font color for edge label */
		newsg->ed_fcolor = rootedon->ed_fcolor;
		/* font for label */
		newsg->ed_ffontname = rootedon->ed_ffontname;
		/* edge line thickness */
		newsg->ed_thickness = rootedon->ed_thickness;
		/* label */
		newsg->ed_label = rootedon->ed_label;
		/* line style */
		newsg->ed_style = rootedon->ed_style;
		/* fontsize */
		newsg->ed_fontsize = rootedon->ed_fontsize;
		/* color scheme */
		newsg->ed_cs = rootedon->ed_cs;
	}
	return;
}

/* graph defaults graph[] */
#include "parsedot-gd.c"

/* start of a compound statement { } */
int is_a_dotcompoundgraph(struct usubg *subg)
{
	struct usubg *newsg = NULL;
	char *sgname = NULL;
	char buf[32];
	int ret = 0;

	if (token != DOT_BO) {
		return (0);
	}

	/* it is a compound statement { ... } and create a name for it */
	memset(buf, 0, 32);
	snprintf(buf, (32 - 1), "__compound%d__", ncompound);
	sgname = uniqstring(buf);

	/* create subgraph with name rooted on subg */
	newsg = usubg_new(sgname, subg);

	/* copy node, edge defaults */
	set_subg_defaults(subg, newsg);

	/* create label for subgraph */
	memset(buf, 0, 32);

	/* in display start counting compound with 1 */
	snprintf(buf, (32 - 1), "compound%d", (ncompound + 1));

	newsg->label = uniqstring(buf);
	newsg->summaryn->label = newsg->label;

	/* create uniq node name for summary node */
	memset(buf, 0, 32);
	snprintf(buf, (32 - 1), "__compound%d_%p__", ncompound, (void *)newsg);
	newsg->summaryn->name = uniqstring(buf);

	/* update count */
	ncompound = ncompound + 1;

	/* initially unfolded subgraph */
	newsg->bitflags.folded = 0;

	/* fold/unfold depending on command line option */
	if (option_init_unfolded) {
		newsg->bitflags.folded = 0;
	}

	if (option_init_folded) {
		newsg->bitflags.folded = 1;
	}

	/* add subgraph to list of subgraphs */
	parsedsgl_add(newsg);

	/* configure summary node */

	/* summary node is part of new subgraph */
	newsg->summaryn->rootedon = newsg;

	/* white background color */
	newsg->summaryn->fillcolor = 0x00ffffff;

	/* this is a subgraph summary node */
	newsg->summaryn->bitflags.sumnode = 1;

	/* add summary node to list of summary nodes */
	summarynl_add(newsg->summaryn);

	/* set which is the starting subgraph in root graph */
	if (subg == NULL) {
		/* started in root graph */
		newsg->rootsubgraph = newsg;
		newsg->rootedon = NULL;
	} else {
		/* is a subsubgraph */
		newsg->rootsubgraph = subg->rootsubgraph;
	}

	/* parse compound data until the last brace close */
	if (option_parsedebug) {
		printf("%s(): into compound {...} %s\n", __FUNCTION__, newsg->label);
		fflush(stdout);
	}

	/* XXX fixme a [] can follow {}. this needs to be done different. */
	/* XXX fixme ldd2dot, does not expand correct */

	/* check if there is a pending edge from a subgraph to this subgraph */
	if (pe_lasttoken) {
		/* */
		if (option_parsedebug) {
			printf("%s(): subgraph=%s pe_lasttoken found lastnode=%p lastsubg=%p\n", __FUNCTION__, newsg->label,
			       (void *)pe_lastnode, (void *)pe_lastsubg);
			fflush(stdout);
		}

		/* edge from subgraph to node */
		if (pe_lastsubg) {
			dot_desadd(NULL, pe_lastsubg, NULL, newsg, DES_S_S);
		}

		/* clear because data is handled */
		dot_pe_clear();
	}

	/* parse compound subgraph */
	ret = parsedot2(newsg);

	/* check parse status */
	if (ret) {
		/* parsed oke and set flag for edge */
		pe_lastsubg = newsg;
		pe_lastnode = (struct unode *)0;
	} else {
		/* parse error */
		dot_pe_clear();
	}

/* XXX todo here ldd2dot */
#if 0
/* eat the } */
	token = dotlex();
#endif

	/* return 1/0 */
	return (ret);
}

/* compound edge as in { ... } -> node or { ... } -> { ... } */
static int is_a_dotcedge(struct usubg *subg)
{
	/* expect -> or -- */
	if (token == DOT_UEDGE) {
		/* oke */
	} else if (token == DOT_DEDGE) {
		/* oke */
	} else {
		/* not an edge */
		return (0);
	}
	/* check if pending edge follows a node or subgraph */
	if ((pe_lastnode == NULL) && (pe_lastsubg == NULL)) {
		/* pending edge at random position */
		dot_pe_clear();
		/* error status */
		if (subg) {
			parser_error_string = "un-expected edge -- or -> in subgraph";
		} else {
			parser_error_string = "un-expected edge -- or -> in root graph";
		}
		return (0);
	}
	/* pending edge is oke */
	pe_lasttoken = token;
	return (1);
}

/* clear pending edge data */
void dot_pe_clear(void)
{
	if (option_parsedebug) {
		printf("%s():\n", __FUNCTION__);
		fflush(stdout);
	}
	/* pending edge data last seen */
	pe_lastsubg = NULL;
	pe_lastnode = NULL;
	pe_lasttoken = 0;
	return;
}

/* free pending edge data in splay data tree */
static void dot_pe_free(void)
{
	pet = splay_tree_delete(pet);
	npet = 0;
	return;
}

/*
 * possible dot bugs and issues:
 * - at true/false args it also accepts numbers as 1/0
 * and a whole lot more todo
 */

/* end */
